/**
 * Copyright &copy; 2016-2017 <a href="http://git.oschina.net/whoamien/backend_management">Backend Management</a> All rights reserved.
 */
package com.xw.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;

import org.apache.shiro.codec.Base64;

import com.xw.framework.encryption.ConversionUtil;
import com.xw.framework.encryption.AESUtil;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * The Class RSAUtils.
 *
 * @author Xi Wei
 */
public class RSAUtils {

	/** The Constant RSA_PUBLIC_KEY. */
	public static final String RSA_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxSiMB8lp82LXObB6BJeOankzSvHEbA6IP5eOUmC3sub4TTxqsIqkRRY9SM2irxEyLCzy99PalsZSs5WsfXl4ZY5DD2Y8mdqChdIewGChy6PlcBJNfC5MvlM8iCavNsggcPBtGGtPFbtsnaZIfMsp3/X7Ued62PUgJXiZKyE+P/QIDAQAB";
	
	/** The Constant RSA_PRIVATE_KEY. */
	public static final String RSA_PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALFKIwHyWnzYtc5sHoEl45qeTNK8cRsDog/l45SYLey5vhNPGqwiqRFFj1IzaKvETIsLPL309qWxlKzlax9eXhljkMPZjyZ2oKF0h7AYKHLo+VwEk18Lky+UzyIJq82yCBw8G0Ya08Vu2ydpkh8yynf9ftR53rY9SAleJkrIT4/9AgMBAAECgYEAmiWRpqGn16E9AkuzgisFaGx280vxM8HG6PfDFnHyjD87auJ+mGhSg7ftU1Vrjt1QiByvJaKVyZ9LW8DlkRKl0Q00ozLF6k7GcbE6cs2v155JC3kSjMiOliWmeQUbi0naDZ/vry5lh2i7eeytKCqmx1fm6js1KGErxUFDmSdxcWkCQQDqpG6OnbfDTPkZ/Q23BpY15k4fa7r+S6uM7Q1216prpxuEayIbE98ThP1gI7IZlrJp+J4I+8G+51frNscelfb7AkEAwW1JzZ7FmBthcXZfUAV4wWl3iOJ/6HXgbzqsHxPcFk+AdiS4Q9ZD3ZVa1dU7a2wi9RXecormqfAPguhZ5QjDZwJAMCidlGHklI4Y1m+sv/L3I4roX+SRlZ3cUOlaiRjMnpnGuQ/wCRS0BWKXSoMpC711UvC3JurdIhyDXAB7L9jhvwJASAR58y4HYe/n4wfwyumOoWy0TeoPN60T6DYPOe76BfCJ4vMfDFrtn8XpMS6YVhHIHjqLPViMM3+nNtVuCwGtRwJBAKnDXentutLWn2q9tJbdal8lDNtQYnIHjNOXCg4P8BA1G3H9xwlfTwYTVN2j4ZwF9gBHtMQFeCH5hMD/6N2FhAQ=";

	// RSA key位数
	private static final int KEY_SIZE = 1024;

	// 单次RSA加密操作所允许的最大块长度，该值与 KEY_SIZE、padding方法有关。
	// 1024key->128,2048key->1256,512key->53,11个字节用于保存padding信息。
	private static final int BLOCK_SIZE = 117;

	private static final int OUTPUT_BLOCK_SIZE = 128;

	// private static final int KEY_SIZE = 1024;
	// private static final int BLOCK_SIZE = 117;
	// private static final int OUTPUT_BLOCK_SIZE = 128;

	// private static final int KEY_SIZE = 2048;
	// private static final int BLOCK_SIZE = 245;
	// private static final int OUTPUT_BLOCK_SIZE = 256;

	private static SecureRandom secrand = new SecureRandom();
	
	/** The rsa cipher. */
	public static Cipher rsaCipher;

	/** The Algorithm. */
	public static String Algorithm = "RSA";// RSA、RSA/ECB/PKCS1Padding

	// public static String
	// Algorithm="RSA/ECB/PKCS1Padding";//RSA、RSA/ECB/PKCS1Padding

	/**
	 * 生成密钥对.
	 *
	 * @return KeyPair
	 */
	public static String[] generateRSAKeyPair() {
		String[] keypair = new String[2];
		try {
			KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(Algorithm);
			// 密钥位数
			keyPairGen.initialize(KEY_SIZE);
			// 密钥对
			KeyPair keyPair = keyPairGen.generateKeyPair();

			// 公钥
			PublicKey publicKey = (RSAPublicKey) keyPair.getPublic();

			// 私钥
			PrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

			String publicKeyString = getKeyString(publicKey);
			System.out.println("Public KEY===>" + publicKeyString);
			keypair[0] = publicKeyString;

			String privateKeyString = getKeyString(privateKey);
			System.out.println("Private KEY===>" + privateKeyString);
			keypair[1] = privateKeyString;

		} catch (Exception e) {
			System.err.println("Exception:" + e.getMessage());
		}
		return keypair;
	}

	/**
	 * 生成密钥对.
	 *
	 * @return KeyPair
	 */
	public static KeyPair getRSAKeyPair() {
		try {
			KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(Algorithm);
			// 密钥位数
			keyPairGen.initialize(KEY_SIZE);
			// 密钥对
			KeyPair keyPair = keyPairGen.generateKeyPair();

			return keyPair;

		} catch (Exception e) {
			System.err.println("Exception:" + e.getMessage());
		}
		return null;
	}

	/**
	 * Gets the public key.
	 *
	 * @param key the key
	 * @return the public key
	 */
	public static PublicKey getPublicKey(String key) {
		try {
			byte[] keyBytes;
			keyBytes = new BASE64Decoder().decodeBuffer(key);

			X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
			KeyFactory keyFactory = KeyFactory.getInstance(Algorithm);
			PublicKey publicKey = keyFactory.generatePublic(keySpec);
			return publicKey;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * Gets the public key modulus.
	 *
	 * @param keyPair the key pair
	 * @return the public key modulus
	 */
	public static String getPublicKeyModulus(KeyPair keyPair) {
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
		return publicKey.getModulus().toString(16);
	}

	/**
	 * Gets the max digits.
	 *
	 * @param keyLength the key length
	 * @return the max digits
	 */
	public static int getMaxDigits(int keyLength) {
		return ((keyLength * 2) / 16) + 3;
	}

	/**
	 * Gets the public key exponent.
	 *
	 * @param keyPair the key pair
	 * @return the public key exponent
	 */
	public static String getPublicKeyExponent(KeyPair keyPair) {
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
		return publicKey.getPublicExponent().toString(16);
	}

	/**
	 * Gets the private key.
	 *
	 * @param key the key
	 * @return the private key
	 * @throws Exception the exception
	 */
	public static PrivateKey getPrivateKey(String key) throws Exception {
		byte[] keyBytes;
		keyBytes = new BASE64Decoder().decodeBuffer(key);

		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(Algorithm);
		PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
		return privateKey;
	}

	/**
	 * Gets the key string.
	 *
	 * @param key the key
	 * @return the key string
	 * @throws Exception the exception
	 */
	public static String getKeyString(Key key) throws Exception {
		byte[] keyBytes = key.getEncoded();
		String s = new BASE64Encoder().encode(keyBytes);
		return s;
	}

	/**
	 * 对content采用RSA加密，再BASE64加密.
	 *
	 * @param publicKeyString the public key string
	 * @param content            明文串
	 * @return cardSecretPwd
	 * @throws Exception the exception
	 */
	public static String encodeSecret(String publicKeyString, String content) throws Exception {

		try {
			rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
			throw e;
		}

		Key publicKey = getPublicKey(publicKeyString);
		try {
			// PublicKey pubkey = keys.getPublic();
			rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey, secrand);
			// System.out.println(rsaCipher.getBlockSize());
			// System.out.println(Message.getBytes("utf-8").length);
			// byte[] encryptedData =
			// rsaCipher.doFinal(Message.getBytes("utf-8"));
			byte[] data = content.getBytes("utf-8");
			int blocks = data.length / BLOCK_SIZE;
			int lastBlockSize = data.length % BLOCK_SIZE;
			byte[] encryptedData = new byte[(lastBlockSize == 0 ? blocks : blocks + 1) * OUTPUT_BLOCK_SIZE];
			for (int i = 0; i < blocks; i++) {
				// int thisBlockSize = ( i + 1 ) * BLOCK_SIZE > data.length ?
				// data.length - i * BLOCK_SIZE : BLOCK_SIZE ;
				rsaCipher.doFinal(data, i * BLOCK_SIZE, BLOCK_SIZE, encryptedData, i * OUTPUT_BLOCK_SIZE);
			}
			if (lastBlockSize != 0) {
				rsaCipher.doFinal(data, blocks * BLOCK_SIZE, lastBlockSize, encryptedData, blocks * OUTPUT_BLOCK_SIZE);
			}
			// return new BASE64Encoder().encode(encryptedData);
			return ConversionUtil.byte2hex(encryptedData);

		} catch (InvalidKeyException e) {
			e.printStackTrace();
			throw new IOException("InvalidKey");
		} catch (ShortBufferException e) {
			e.printStackTrace();
			throw new IOException("ShortBuffer");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new IOException("UnsupportedEncoding");
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
			throw new IOException("IllegalBlockSize");
		} catch (BadPaddingException e) {
			e.printStackTrace();
			throw new IOException("BadPadding");
		} finally {

		}
	}

	/**
	 * Encode secret.
	 *
	 * @param keyPair the key pair
	 * @param content the content
	 * @return the string
	 * @throws Exception the exception
	 */
	public static String encodeSecret(KeyPair keyPair, String content) throws Exception {

		try {
			rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
			throw e;
		}

		Key publicKey = keyPair.getPublic();
		try {
			// PublicKey pubkey = keys.getPublic();
			rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey, secrand);
			// System.out.println(rsaCipher.getBlockSize());
			// System.out.println(Message.getBytes("utf-8").length);
			// byte[] encryptedData =
			// rsaCipher.doFinal(Message.getBytes("utf-8"));
			byte[] data = content.getBytes("utf-8");
			int blocks = data.length / BLOCK_SIZE;
			int lastBlockSize = data.length % BLOCK_SIZE;
			byte[] encryptedData = new byte[(lastBlockSize == 0 ? blocks : blocks + 1) * OUTPUT_BLOCK_SIZE];
			for (int i = 0; i < blocks; i++) {
				// int thisBlockSize = ( i + 1 ) * BLOCK_SIZE > data.length ?
				// data.length - i * BLOCK_SIZE : BLOCK_SIZE ;
				rsaCipher.doFinal(data, i * BLOCK_SIZE, BLOCK_SIZE, encryptedData, i * OUTPUT_BLOCK_SIZE);
			}
			if (lastBlockSize != 0) {
				rsaCipher.doFinal(data, blocks * BLOCK_SIZE, lastBlockSize, encryptedData, blocks * OUTPUT_BLOCK_SIZE);
			}
			return new BASE64Encoder().encode(encryptedData);

		} catch (InvalidKeyException e) {
			e.printStackTrace();
			throw new IOException("InvalidKey");
		} catch (ShortBufferException e) {
			e.printStackTrace();
			throw new IOException("ShortBuffer");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new IOException("UnsupportedEncoding");
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
			throw new IOException("IllegalBlockSize");
		} catch (BadPaddingException e) {
			e.printStackTrace();
			throw new IOException("BadPadding");
		} finally {

		}
	}

	/**
	 * 对content采用RSA加密，再BASE64加密.
	 *
	 * @param privateKeyString            私钥值
	 * @param content            明文串
	 * @return cardSecretPwd
	 * @throws Exception the exception
	 */
	public static String encodeSecretByPriKey(String privateKeyString, String content) throws Exception {

		try {
			rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
			throw e;
		}

		Key privateKey = getPrivateKey(privateKeyString);
		try {
			// PublicKey pubkey = keys.getPublic();
			rsaCipher.init(Cipher.ENCRYPT_MODE, privateKey, secrand);
			// System.out.println(rsaCipher.getBlockSize());
			// System.out.println(Message.getBytes("utf-8").length);
			// byte[] encryptedData =
			// rsaCipher.doFinal(Message.getBytes("utf-8"));
			byte[] data = content.getBytes("utf-8");
			int blocks = data.length / BLOCK_SIZE;
			int lastBlockSize = data.length % BLOCK_SIZE;
			byte[] encryptedData = new byte[(lastBlockSize == 0 ? blocks : blocks + 1) * OUTPUT_BLOCK_SIZE];
			for (int i = 0; i < blocks; i++) {
				// int thisBlockSize = ( i + 1 ) * BLOCK_SIZE > data.length ?
				// data.length - i * BLOCK_SIZE : BLOCK_SIZE ;
				rsaCipher.doFinal(data, i * BLOCK_SIZE, BLOCK_SIZE, encryptedData, i * OUTPUT_BLOCK_SIZE);
			}
			if (lastBlockSize != 0) {
				rsaCipher.doFinal(data, blocks * BLOCK_SIZE, lastBlockSize, encryptedData, blocks * OUTPUT_BLOCK_SIZE);
			}
			return ConversionUtil.byte2hex(encryptedData);

		} catch (InvalidKeyException e) {
			e.printStackTrace();
			throw new IOException("InvalidKey");
		} catch (ShortBufferException e) {
			e.printStackTrace();
			throw new IOException("ShortBuffer");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new IOException("UnsupportedEncoding");
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
			throw new IOException("IllegalBlockSize");
		} catch (BadPaddingException e) {
			e.printStackTrace();
			throw new IOException("BadPadding");
		} finally {

		}
	}

	/**
	 * Decode.
	 *
	 * @param content the content
	 * @return the string
	 */
	public static String decode(String content) {
		try {
			return decodeSecret(RSA_PRIVATE_KEY, content);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return "";
		}
	}

	/**
	 * Encode.
	 *
	 * @param content the content
	 * @return the string
	 */
	public static String encode(String content) {
		try {
			return encodeSecret(RSA_PUBLIC_KEY, content);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return content;
		}
	}

	/**
	 * BASE64解密，再RSA解密.
	 *
	 * @param privateKeyString the private key string
	 * @param content            密文串
	 * @return 用私钥解密串
	 */
	public static String decodeSecret(String privateKeyString, String content) {

		try {
			rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
			return "";
		}

		byte[] decoded = null;
		try {
			// decoded = new BASE64Decoder().decodeBuffer(content);
			decoded = ConversionUtil.hex2byte(content);
		} catch (Exception e1) {
			e1.printStackTrace();
		}

		try {
			Key privateKey = getPrivateKey(privateKeyString);
			rsaCipher.init(Cipher.DECRYPT_MODE, privateKey, secrand);
			int blocks = decoded.length / OUTPUT_BLOCK_SIZE;
			ByteArrayOutputStream decodedStream = new ByteArrayOutputStream(decoded.length);
			for (int i = 0; i < blocks; i++) {
				decodedStream.write(rsaCipher.doFinal(decoded, i * OUTPUT_BLOCK_SIZE, OUTPUT_BLOCK_SIZE));
			}
			return new String(decodedStream.toByteArray(), "UTF-8");
		} catch (Exception e) {
			e.printStackTrace();
			return "";
		} finally {

		}
	}

	/**
	 * Decode secret.
	 *
	 * @param keyPair the key pair
	 * @param content the content
	 * @return the string
	 */
	public static String decodeSecret(KeyPair keyPair, String content) {

		try {
			rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
		} catch (Exception e) {
			e.printStackTrace();
			return "";
		}

		byte[] decoded = null;
		try {
			decoded = new BASE64Decoder().decodeBuffer(content);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		Key privateKey = keyPair.getPrivate();
		try {
			rsaCipher.init(Cipher.DECRYPT_MODE, privateKey, secrand);
			int blocks = decoded.length / OUTPUT_BLOCK_SIZE;
			ByteArrayOutputStream decodedStream = new ByteArrayOutputStream(decoded.length);
			for (int i = 0; i < blocks; i++) {
				decodedStream.write(rsaCipher.doFinal(decoded, i * OUTPUT_BLOCK_SIZE, OUTPUT_BLOCK_SIZE));
			}
			return new String(decodedStream.toByteArray(), "UTF-8");
		} catch (Exception e) {
			e.printStackTrace();
			return "";
		} finally {

		}
	}

	/**
	 * BASE64解密，再RSA解密.
	 *
	 * @param publicKeyString the public key string
	 * @param content            密文串
	 * @return 用私钥解密串
	 * @throws Exception the exception
	 */
	public static String decodeSecretByPubKey(String publicKeyString, String content) throws Exception {

		try {
			rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
			throw e;
		}

		byte[] decoded = null;
		try {
			// decoded = new BASE64Decoder().decodeBuffer(content);
			decoded = ConversionUtil.hex2byte(content);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		Key publicKey = getPublicKey(publicKeyString);
		try {
			rsaCipher.init(Cipher.DECRYPT_MODE, publicKey, secrand);
			int blocks = decoded.length / OUTPUT_BLOCK_SIZE;
			ByteArrayOutputStream decodedStream = new ByteArrayOutputStream(decoded.length);
			for (int i = 0; i < blocks; i++) {
				decodedStream.write(rsaCipher.doFinal(decoded, i * OUTPUT_BLOCK_SIZE, OUTPUT_BLOCK_SIZE));
			}
			return new String(decodedStream.toByteArray(), "UTF-8");
		} catch (InvalidKeyException e) {
			e.printStackTrace();
			throw new IOException("InvalidKey");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new IOException("UnsupportedEncoding");
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
			throw new IOException("IllegalBlockSize");
		} catch (BadPaddingException e) {
			e.printStackTrace();
			throw new IOException("BadPadding");
		} finally {

		}
	}

	/**
	 * Test.
	 *
	 * @throws Exception the exception
	 */
	public void test() throws Exception {
		System.out.println("Base64:" + Base64.encodeToString("abc".getBytes()));
		// 密钥对
		/*
		 * String[] keys=generateRSAKeyPair(); System.out.println("private key:"
		 * + keys[1]); System.out.println("public key:" + keys[0]);
		 */
		String priKey = ConvertUtils.txt2String(getClass().getResourceAsStream("/rsa_1024_priv.pem"));
		;
		String pubKey = ConvertUtils.txt2String(getClass().getResourceAsStream("/rsa_1024_pub.pem"));
		;
		;
		String s = "xiwei";

		// 公钥加密
		String d = encodeSecret(pubKey, s);
		System.out.println("公钥加密密文：" + d);

		// 私钥解密
		String c = decodeSecret(priKey, d);

		System.out.println("私钥解密明文：" + c);

		// 私钥加密
		String e = encodeSecretByPriKey(priKey, s);
		System.out.println("私钥加密密文：" + e);

		// 公钥解密
		String f = decodeSecretByPubKey(pubKey, e);

		System.out.println("公钥解密明文：" + f);

		String url = "rediectUrl=&username=admin&password=Password";
		String u = encodeSecret(pubKey, url);
		System.out.println("URL 加密；" + u);
		String add = "06e5ec7effa4d61d9448851b12e984eac4e2489fca6e4b54ad8438ed8b13e0ed4295f39bf959954c0b225a6187537c9e";
		String r = decodeSecret(priKey, add);
		System.out.println("另外的解密：" + r);

		String aeskey = "c2f65b02336b262dfd7d5a252370ccdd";
		String aesEncryptWithRSA = "7414c373b3544128bdfe4526e2f649a97408636e55ef9366a422fbb85ae09a9ce1914acbbf096def39a88f0df60e1200d3057b59d306de125bde2ae0758d94e882b284f2e4f59a7db202dce11ae5ec1a9c7bed42eb58defbf8db02883b73448cf69512693992449e4236a9c79a7166349415de34e5ca41d9325987520be51eaf";

		String decrypted = RSAUtils.decodeSecret(priKey, aesEncryptWithRSA);
		System.out.println("decrypted=" + decrypted);

		String aesEncrypt = AESUtil.encryptForJS(decrypted, decrypted);
		System.out.println("aesEncrypt=" + aesEncrypt);
	}

	/**
	 * The main method.
	 *
	 * @param args the arguments
	 * @throws Exception the exception
	 */
	public static void main(String[] args) throws Exception {

		RSAUtils util = new RSAUtils();
		util.test();
	}
}
